package scalab.core.component

import scalab.core.Cachable
import scalab.core.interpreter.Settings
import scalab.caches.NoCache
import java.io.File
import scala.collection.immutable.{Set, HashSet}
import scalab.core.gates.{Input, Output}
import scalab.core.dependencies.{Dependency, HardDependency, InterDependency, FilesInterDependencies}
//can be transformed in trait...since name is a field...
trait ExecutableComponent extends SinkComponent with Cachable{

	import scalab.core.event._
	private var executionStartedListeners: List[Listener[ExecutionStarted]] = Nil
	private var executionFinishedListeners: List[Listener[ExecutionFinished]] = Nil
	def listenExecutionStarted(listener: ExecutionStarted => Unit){
			executionStartedListeners = Listener[ExecutionStarted](listener) :: executionStartedListeners
	}
	def listenExecutionFinished(listener: ExecutionFinished => Unit){
			executionFinishedListeners = Listener[ExecutionFinished](listener) :: executionFinishedListeners
	}
	private[core] def fireExecutionStarted{
		val event = ExecutionStarted(this)
		executionStartedListeners foreach {l => l(event)}
	}
	private[core] def fireExecutionFinished(succeeded: Boolean){
		val event = ExecutionFinished(this, succeeded)
		executionFinishedListeners foreach {l => l(event)}
	}

	private[core] def exec: Boolean

	private[core] def reset{
		interDependencies.clear
		out.setDeps(HashSet.empty[Dependency])
	}

	private[core] def updateId: String = {
		import scalab.utils.MD5
		val strs: List[String] = 
			instanceId + className :: (inputs map {i => i.updateId})
		identifier = MD5.digest(strs)
		//warning(identifier)
		//warning("instanceId: " + instanceId)
		identifier
	}

	/**
	* Files relations
	*/
	//transform in immutable?
	lazy val interDependencies: FilesInterDependencies = new FilesInterDependencies

	/**
	* Indicates that the resource designated by the <code>target</code> path has a 
	*	relation with the resources corresponding to the <code>needles</code> paths 
	*	with respect to <code>this ExecutableComponent</code>. 
	*	Formally, this means that if there exists a resource <code>n</code> in 
	*	<code>needles</code> such that <code>n</code> is out-of-date, this implies 
	*	that the <code>target</code> resource is also out-of-date.
	*
	*	@returns <code>this ExecutableComponent</code>
	*/
	def require(target: Dependency, needles: Iterable[Dependency]): ExecutableComponent = {
		interDependencies.require(target,needles)
		this
	}

	def require(target: Dependency, needles: Dependency*): ExecutableComponent = require(target, needles)
	def requireResources(target: String, needles: Iterable[String]): ExecutableComponent = {
		require(HardDependency(target), needles map {n => HardDependency(n)})
	}
	def requireResources(target: String, needles: String*): ExecutableComponent = requireResources(target, needles)
	def requireResources(target: File, needles: Iterable[File]): ExecutableComponent = {
		require(HardDependency(target), needles map {n => HardDependency(n)})
	}
	def requireResources(target: File, needles: File*): ExecutableComponent = requireResources(target, needles)

	//implement for files (and dependencies...)

	/**
	* Indicates a mutual relation among the resources designated by <code>paths</code>. 
	*	Formally, this means that if any resource <code>r</code> in the resources 
	*	designated by <code>paths</code> is out-of-date, this implies that all those 
	*	resources are also out-of-date.
	*
	*	@returns <code>this ExecutableComponent</code>
	*/
	def related(paths: Iterable[Dependency]): ExecutableComponent = {
		interDependencies related paths
		this
	}

	def related(paths: Dependency*): ExecutableComponent = related(paths)

	//if a dependency is invalidated in an input, it is
	// the case in all inputs => does only affect performances 
	/*final def invalidate(dep: Dependency) =
		inputs foreach {i => i.invalidate(dep)}

	final def invalidate(deps: Iterable[Dependency]) = 
		inputs foreach {i => i.invalidate(deps)}*/

	final def invalidateInterDependency(rel: InterDependency, invalidated: Set[InterDependency]){
		if(!invalidated.contains(rel)){
			invalidate(rel.dependency)
			rel.children foreach {r => invalidateInterDependency(r, invalidated + rel)}
		}
	}

	final def invalidateProduct(product: InterDependency) =
		product.children foreach {c => invalidateInterDependency(c, HashSet.empty[InterDependency])}

	final def invalidateProducts(products: Iterable[InterDependency]) =
		products foreach {p => invalidateProduct(p)}

	/*final def invalidate{
		invalidate(dependencies)
	}*/
	

	/*private[core] def validate(validated: Set[Buildable]): Pair[Boolean, Set[Buildable]] = {
		debug("validate " + name)
		if(!Settings.preserveCache && hasUniverse)
			setCache(NoCache)
		//inputs forall {i => i.validate}
		utils.AlgorithmFactory.createValidate(inputs)(validated)
	}*/

	final private[core] def validate(
		visited: Set[Component], 
		cycles: Set[(Output, Input)], 
		validated: Set[SinkComponent]): (
			Boolean, 
			Set[Component], 
			Set[(Output, Input)], 
			Set[SinkComponent]
		) = {
			if(!Settings.preserveCache && hasUniverse)
				setCache(NoCache())
			utils.AlgorithmFactory.createValidate(this)(visited, cycles, validated)
		}
  //TODO: with memory => does not aleays execute... (locally-up-to-date)
  //also in Buildable?
  //protected[core] var oldDependencies: HashSet[Dependency] = new HashSet[Dependency]
  //private[core] def locallyUpToDate: Boolean = 
  //  executed 
		//&& sources.dependencies == oldDependencies
    //&& task not changed && digests equals

	/*private[core] override def update(fresh: Set[ExecutableComponent]): Pair[Boolean, Set[ExecutableComponent]] = {
		debug("update")
		utils.AlgorithmFactory.createUpdate(this, inputs)(fresh)
		//if(res && exec){
		//	error("passe!")
		//	(true, newFresh + this)
		//}
		//else
		//	(false, newFresh)//keep going mode?
	}*/

	final private[core] def update(
		visited: Set[Component], 
		cycles: Set[(Output, Input)], 
		fresh: Set[ExecutableComponent]): (
			Boolean, 
			Set[Component], 
			Set[(Output, Input)], 
			Set[ExecutableComponent]
		) = {
			fireUpdateStarted
			val res = utils.AlgorithmFactory.createUpdate(this)(visited, cycles, fresh)
			fireUpdateFinished(res._1)
			res
		}

	final private[core] def upToDate(
		products: Set[Resource],
		visited: Set[Component], 
		cycles: Set[(Output, Input)], 
		fresh: Set[ExecutableComponent]): (
			Boolean, 
			Set[Component], 
			Set[(Output, Input)], 
			Set[ExecutableComponent]
		) = utils.AlgorithmFactory.createUpToDate(this)(products, visited, cycles, fresh)

	def locallyUpToDate(products: Set[Resource]): Boolean
  /*override private[core] def update: Boolean = {
		debug("update")
    (inputs forall {i => i.update}) && //updates sources
		exec //execute task, if needed or redirect from cache
		//(locallyUpToDate || exec)
    //(locallyUpToDate || (exec && update)) //either nothing changed or execute and (update)
	}*/

	//override is not mandatory but shows that value has been declared before
	override val out: Output = {
		val self = this
		new Output{
			val component = self
		}
	}

	ExecutableComponent.allExecComps = ExecutableComponent.allExecComps + this
}

object ExecutableComponent{
	private[core] var allExecComps: Set[ExecutableComponent] = HashSet.empty[ExecutableComponent]
	def getAll: Set[ExecutableComponent] = allExecComps
}
